OAuth2 定義兩種 Client 類型:confidential 與 public 兩種。之前不管 OAuth2 或是 OpenID Connect,在說明流程的時候,都是以 confidential client,搭配較安全的 Authorization Code Grant / Authorization Code Flow 作為範例,但終究還是會面對相較不安全的 public client。
在 RFC 7636 裡有提到 public client 容易受到攻擊的方法之一--authorization code interception attack,中文直譯為「授權碼攔截攻擊」,顧名思義,它的攻擊手法基礎正是把 authorization code 偷走。
之前討論到 OAuth2 的 Authorization Code Grant 或 OpenID Connect 時,有提到 code 用途主要是拿來交換 token;而在簡介 HTTP 協定有提到,HTTP 的特色即是無狀態,也就是只要請求內容一模一樣,則它無法分辨請求是否是惡意程式所發出的,因此只要攻擊者能偽造請求,即可成功使用 code 換取 token。
那回頭看一下 Client 會傳什麼資訊給 token endpoint:
grant_type
code
redirect_uri
除了上面三個參數外,另外還需要傳 Client ID 與 secret 給授權服務器驗證,即可換到 token。理論上憑證資訊與 token 都是不能外流的,但 public client 的特色即是 Client 處於相較不安全的環境,如 Native App 只要匯出後解開,即可找到憑證資訊。
以 Native App 為例,攻擊手法的時序圖如下:
@startuml
participant LegitimateApp
participant MaliciousApp
participant Browser
participant AuthorizationServer
LegitimateApp -> Browser: (1) Authorization Request
Browser -> AuthorizationServer: (2) Authorization Request
Browser <- AuthorizationServer: (3) Authorization Code
LegitimateApp <- Browser: (4) Authorization Code
|||
MaliciousApp <-- Browser: (4) Authorization Code
MaliciousApp --> AuthorizationServer: (5) Authorization Grant
MaliciousApp <-- AuthorizationServer: (6) Access Token
@enduml
詳細說明如下:
redirect_uri
只能設定成自定義 URI 才能順利引導使用者回到 App 裡從流程圖上來看,這個攻擊是非常容易實現的,而 RFC 7636 正是定義如何防範此問題。
首先先來定義專有名詞:
在 Client 發出的授權請求前,先保存一個參數在儲存空間裡,稱之為 code verifier,值為一個密碼學安全的僞隨機數。接著 Client 使用 code_verifier 搭配 code challenge method 產生另一個值稱之為 code challenge。方法 RFC 裡定義有兩種:
在發送授權請求的時候,多定義了下面這兩個參數:
名稱 | 必要 |
---|---|
code_challenge | REQUIRED |
code_challenge_method | OPTIONAL |
授權伺服器在收到 code_challenge
時,必須將它與回應的 code
做關聯。
接著在 Client 收到 code
後,發出 token 請求時,多帶 code verifier:
名稱 | 必要 |
---|---|
code_verifier | REQUIRED |
授權伺服器收到 code_verifier
與 code
之後,先把 code
關聯的 code_challenge
與 code_challenge_method
找到後,使用 code_verifier
搭配關聯到的 code_challenge_method
產生 challenge,再跟 code_challenge
比較,即可知道發出 token 請求的 Client 與當初發授權請求的是同一個 Client。
@startuml
participant LegitimateApp
participant MaliciousApp
participant Browser
participant AuthorizationServer
LegitimateApp -> LegitimateApp: (1) Generate code_verifier and code_challenge
LegitimateApp -> Browser: (1) Authorization Request with code_challenge
Browser -> AuthorizationServer: (2) Authorization Request with code_challenge
AuthorizationServer -> AuthorizationServer: (2) Binding Code and code_challenge
Browser <- AuthorizationServer: (3) Authorization Code
LegitimateApp <- Browser: (4) Authorization Code
LegitimateApp -> AuthorizationServer: (4) Authorization Grant with code_verifier
LegitimateApp <- AuthorizationServer: (4) Access Token
|||
MaliciousApp <-- Browser: (4) Authorization Code
MaliciousApp --> AuthorizationServer: (5) Authorization Grant without code_verifier
MaliciousApp <-- AuthorizationServer: (6) Error Response
@enduml
這裡的步驟跟前面說明的一樣,主要是第 5 步,因為即便惡意 App 拿到了 code_challenge
,也無法回推第 5 步要傳給授權服務器的 code_verifier
,因此即能有效阻檔「授權碼攔截攻擊」